package org.apache.hadoop.hive.shims;

import java.util.HashMap;
import java.util.Map;

import org.apache.hadoop.hive.thrift.HadoopThriftAuthBridge;
import org.apache.hadoop.util.VersionInfo;
import org.apache.log4j.AppenderSkeleton;

public abstract class ShimLoader {
    public static String HADOOP20SVERSIONNAME = "0.20S";

    public static String HADOOP23VERSIONNAME = "0.23";

    private static HadoopShims hadoopShims;

    private static JettyShims jettyShims;

    private static AppenderSkeleton eventCounter;

    private static HadoopThriftAuthBridge hadoopThriftAuthBridge;

    private static SchedulerShim schedulerShim;

    private static final HashMap<String, String> HADOOP_SHIM_CLASSES = new HashMap<>();

    static {
        HADOOP_SHIM_CLASSES.put(HADOOP20SVERSIONNAME, "org.apache.hadoop.hive.shims.Hadoop20SShims");
        HADOOP_SHIM_CLASSES.put(HADOOP23VERSIONNAME, "org.apache.hadoop.hive.shims.Hadoop23Shims");
    }

    private static final HashMap<String, String> JETTY_SHIM_CLASSES = new HashMap<>();

    static {
        JETTY_SHIM_CLASSES.put(HADOOP20SVERSIONNAME, "org.apache.hadoop.hive.shims.Jetty20SShims");
        JETTY_SHIM_CLASSES.put(HADOOP23VERSIONNAME, "org.apache.hadoop.hive.shims.Jetty23Shims");
    }

    private static final HashMap<String, String> EVENT_COUNTER_SHIM_CLASSES = new HashMap<>();

    static {
        EVENT_COUNTER_SHIM_CLASSES.put(HADOOP20SVERSIONNAME, "org.apache.hadoop.log.metrics.EventCounter");
        EVENT_COUNTER_SHIM_CLASSES.put(HADOOP23VERSIONNAME, "org.apache.hadoop.log.metrics.EventCounter");
    }

    private static final HashMap<String, String> HADOOP_THRIFT_AUTH_BRIDGE_CLASSES = new HashMap<>();

    private static final String SCHEDULER_SHIM_CLASSE = "org.apache.hadoop.hive.schshim.FairSchedulerShim";

    static {
        HADOOP_THRIFT_AUTH_BRIDGE_CLASSES.put(HADOOP20SVERSIONNAME, "org.apache.hadoop.hive.thrift.HadoopThriftAuthBridge");
        HADOOP_THRIFT_AUTH_BRIDGE_CLASSES.put(HADOOP23VERSIONNAME, "org.apache.hadoop.hive.thrift.HadoopThriftAuthBridge23");
    }

    public static synchronized HadoopShims getHadoopShims() {
        if (hadoopShims == null)
            hadoopShims = loadShims(HADOOP_SHIM_CLASSES, HadoopShims.class);
        return hadoopShims;
    }

    public static synchronized JettyShims getJettyShims() {
        if (jettyShims == null)
            jettyShims = loadShims(JETTY_SHIM_CLASSES, JettyShims.class);
        return jettyShims;
    }

    public static synchronized AppenderSkeleton getEventCounter() {
        if (eventCounter == null)
            eventCounter = loadShims(EVENT_COUNTER_SHIM_CLASSES, AppenderSkeleton.class);
        return eventCounter;
    }

    public static synchronized HadoopThriftAuthBridge getHadoopThriftAuthBridge() {
        if (hadoopThriftAuthBridge == null)
            hadoopThriftAuthBridge = loadShims(HADOOP_THRIFT_AUTH_BRIDGE_CLASSES, HadoopThriftAuthBridge.class);
        return hadoopThriftAuthBridge;
    }

    public static synchronized SchedulerShim getSchedulerShims() {
        if (schedulerShim == null)
            schedulerShim = createShim("org.apache.hadoop.hive.schshim.FairSchedulerShim", SchedulerShim.class);
        return schedulerShim;
    }

    private static <T> T loadShims(Map<String, String> classMap, Class<T> xface) {
        String vers = getMajorVersion();
        String className = classMap.get(vers);
        return createShim(className, xface);
    }

    private static <T> T createShim(String className, Class<T> xface) {
        try {
            Class<?> clazz = Class.forName(className);
            return xface.cast(clazz.newInstance());
        } catch (Exception e) {
            throw new RuntimeException("Could not load shims in class " + className, e);
        }
    }

    public static String getMajorVersion() {
        String vers = VersionInfo.getVersion();
        String[] parts = vers.split("\\.");
        if (parts.length < 2)
            throw new RuntimeException("Illegal Hadoop Version: " + vers + " (expected A.B.* format)");
        switch (Integer.parseInt(parts[0])) {
            case 1:
                return HADOOP20SVERSIONNAME;
            case 2:
            case 3:
                return HADOOP23VERSIONNAME;
        }
        throw new IllegalArgumentException("Unrecognized Hadoop major version number: " + vers);
    }
}
